package licode.unisop.auth.application.uniclient;

import cn.hutool.core.util.StrUtil;
import licode.unisop.auth.application.executor.CaptchaSetting;
import licode.unisop.auth.application.gateway.AuthCodeCheckGateway;
import licode.unisop.auth.application.gateway.StrategyAuthGateway;
import licode.unisop.auth.application.utils.AuthIPUtil;
import licode.unisop.auth.application.utils.UserUtil;
import licode.unisop.auth.application.vo.CaptchaIn;
import licode.unisop.auth.application.vo.UserRoleInfo;
import licode.unisop.auth.client.api.CodeValidateService;
import licode.unisop.auth.client.api.UniAuthorService;
import licode.unisop.auth.client.info.*;
import licode.unisop.auth.domain.platform.gateway.PlatInfoGateway;
import licode.unisop.auth.domain.user.service.UserAuthService;
import licode.unisop.auth.domain.user.service.UserInfoService;
import licode.unisop.auth.exception.AuthErrorCode;
import licode.unisop.client.info.SopAuthDirectIn;
import licode.unisop.client.info.SopTokenInfo;
import licode.unisop.client.info.SopUpdatePasswordIn;
import licode.unisop.client.info.SopUserIdIn;
import licode.unisop.client.utils.IPUtils;
import licode.unisop.client.utils.SopAuthConst;
import licode.unisop.client.utils.StringUtil;
import licode.unisop.client.vo.*;
import licode.unisop.log.client.api.LastLoginLogApi;
import licode.unisop.log.client.api.LoginLogApi;
import licode.unisop.log.client.dto.LoginLogDTO;
import licode.unisop.oidc.auth.conf.GrantType;
import licode.unisop.platform.client.api.SiPlatformAuthService;
import licode.unisop.platform.client.api.SiPlatformQueryService;
import licode.unisop.platform.client.info.PlatformAuthInfo;
import licode.unisop.platform.client.vo.PlatAuthVO;
import licode.unisop.provider.conf.SiAssetType;
import licode.unisop.provider.info.*;
import licode.unisop.provider.vo.SiAssetBrief;
import licode.unisop.provider.vo.SiPlatform;
import licode.unisop.provider.vo.SiUser;
import licode.unisop.realm.client.api.SiRealmQueryService;
import licode.unisop.realm.client.api.SiUserService;
import licode.unisop.realm.client.info.SiRealmGrantPlatformIn;
import licode.unisop.types.AuthConst;
import licode.unisop.types.dp.PlatformState;
import licode.unisop.types.dp.SizeConstDef;
import licode.unisop.types.util.AuStrUtil;
import licode.unisop.unilite.result.UniResult;
import licode.unisop.unilite.utils.TryMethodCall;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;

/**
 * 处理用户认证用例
 *
 * @author licode
 */
@Primary
@Service
public class SopAuthorExecutor implements UniAuthorService {
    @Resource
    private UserUtil userUtil;

    @Resource
    private HttpServletResponse response;

    @Resource
    private CaptchaSetting captchaSetting;

    @Resource
    private UserInfoService userInfoService;

    @Resource
    private UserAuthService userAuthService;

    @Resource
    private SiUserService siUserService;

    @Resource
    private SiRealmQueryService siRealmQueryService;

    @Resource
    private PlatInfoGateway platInfoGateway;

    @Resource
    private LoginLogApi loginLogApi;

    @Resource
    private LastLoginLogApi lastLoginLogApi;

    @Resource
    private CodeValidateService codeValidateService;

    @Resource
    private StrategyAuthGateway strategyAuthGateway;

    @Resource
    private AuthCodeCheckGateway authCodeCheckGateway;

    @Resource
    private SiPlatformAuthService siPlatformAuthService;

    @Resource
    private SiPlatformQueryService siPlatformQueryService;

    public void checkCaptcha(CaptchaIn captchaIn) {
        boolean needCaptcha = !Integer.valueOf(1).equals(captchaIn.getNoCaptcha());
        if (needCaptcha && captchaSetting.getEnabled()) {
            codeValidateService.checkCaptcha(captchaIn.getKey(), captchaIn.getCode());
        }
    }

    /**
     * 通过登录信息认证码
     *
     * @param captchaIn 验证码信息
     * @return 返回认证码
     */
    public SopAuthResult authorize(CaptchaIn captchaIn, LoginContextIn req) {
        checkCaptcha(captchaIn);
        return authorize(req);
    }

    @Override
    public SopAuthResult authorize(LoginContextIn req) {
        LoginCheckResult result = handleLogin(req);
        SiCodeInfo codeInfo = userAuthService.authorize(result.getContext());

        return doCodeInfo(codeInfo);
    }

    public SopAuthResult autoSso(AuthDetailIn auth) {
        SiPlatform plInfo = null;
        String platformId;

        // 主会话TOKEN为空，直接返回
        if (StrUtil.isBlank(auth.getSsoToken())) {
            return null;
        }

        // 检查平台
        if (null != auth.getPlatformId()) {
            plInfo = checkPlatform(auth.getPlatformId(), auth.getRealmId());
        }

        platformId = plInfo != null ? plInfo.getId() : "";
        SiAssetBrief assetClient = checkClientInfo(platformId, auth.getClientId(), auth.getClientSecret());

        SiCodeInfo codeInfo = userAuthService.autoSso(SiAuthContextIn
                .builder()
                .realmId(auth.getRealmId())
                .terminal(auth.getTerminal())
                .tenantId(auth.getTenantId())
                .platformId(platformId)
                .peerIp(IPUtils.getIpAddr(getRequest()))
                .ssoToken(auth.getSsoToken())
                .clientId(auth.getClientId())
                .clientSecret(auth.getClientSecret())
                .accessExpired(assetClient.getProps().getInt("accessTimeout", 600))
                .refreshExpired(assetClient.getProps().getInt("refreshTimeout", 600))
                .ticketUri(null != plInfo ? plInfo.getTicketUrl() : auth.getTicketUrl())
                .build());

        return doCodeInfo(codeInfo);
    }

    public SopAuthResult doCodeInfo(SiCodeInfo codeInfo) {
        if (null != codeInfo) {
            setTokenValueToResponseHeader();
            if (StrUtil.isNotBlank(codeInfo.getSsoToken())) {
                // 创建一个 cookie对象
                Cookie cookie = new Cookie(AuthConst.UNI_MASTER_SESSION, codeInfo.getSsoToken());
                cookie.setMaxAge(60 * 60);
                cookie.setPath("/");
                //cookie.setDomain("localhost:5173");
                // 将cookie对象加入response响应
                response.addCookie(cookie);
            }
            return SopAuthResult.builder()
                    .ssoToken(codeInfo.getSsoToken())
                    .redirectUri(codeInfo.getRedirectUri())
                    .build();
        } else {
            return null;
        }
    }

    public void setTokenValueToResponseHeader() {
        // 写入到响应头
        // 此处必须在响应头里指定 Access-Control-Expose-Headers: token-name，否则前端无法读取到这个响应头
        //response.addHeader("Access-Control-Expose-Headers", "wangrong-licode");
    }

    private void checkLoginContextIn(LoginContextIn req) {
        if (null == req.getAccount()) {
            throw AuthErrorCode.USER_INVALID_ACCOUNT.buildThrow();
        }

        if (null == req.getAuth()) {
            throw AuthErrorCode.USER_INVALID_AUTH.buildThrow();
        }

        if (null == req.getAuthDetail()) {
            throw AuthErrorCode.USER_LACK_OF_CLIENT_INFO.buildThrow();
        }
    }

    private SiPlatform checkPlatform(String platformId, String realmId) {
        SiPlatform plInfo = platInfoGateway.getPlatformAndCheck(SiPlatIdFind.builder()
                .platformId(platformId).build());
        if (null == plInfo) {
            throw AuthErrorCode.PLATFORM_NOT_AUTH.buildThrow();
        }
        if (!PlatformState.ACTIVATED.getState().equals(plInfo.getState())) {
            throw AuthErrorCode.PLATFORM_WRONG_STATE.buildThrow();
        }

        if (StrUtil.isBlank(realmId) && StrUtil.isNotBlank(plInfo.getRealmId())) {
            return plInfo;
        } else if (StrUtil.isNotBlank(realmId) && realmId.equals(plInfo.getRealmId())) {
            return plInfo;
        } else {
            if (siRealmQueryService.realmGrantToPlatform(SiRealmGrantPlatformIn
                    .builder().platformId(platformId).realmId(realmId).build()) < 0) {
                throw AuthErrorCode.PLATFORM_NO_GRANT_REALM.buildThrow();
            }
            return plInfo;
        }
    }

    private SiAssetBrief checkClientInfo(String platformId, String clientId, String clientSecret) {
        SiAssetBrief asset = null;
        if (StrUtil.isNotBlank(platformId) && StrUtil.isNotBlank(clientId)) {
            asset = siPlatformQueryService.lookupAuthAssetByCode(SiAssetCodeLookupIn.builder()
                    .platformId(platformId)
                    .assetType(SiAssetType.AT_CLIENT)
                    .code(clientId)
                    .build());

            if (null == asset) {
                throw AuthErrorCode.CLIENT_ID_NOT_EXIST.buildThrow();
            }

            if (null == asset.getProps() ||
                    !asset.getProps().getString("secret", "").equals(clientSecret)) {
                throw AuthErrorCode.CLIENT_SECRET_WRONG.buildThrow();
            }
        } else {
            throw AuthErrorCode.CLIENT_INFO_NO_EXIST.buildThrow();
        }

        return asset;
    }

    /**
     * 通过ticket票据换取登录信息（调用此方法之前必须调用authorize方法生成ticket）
     *
     * @param req ticket信息
     * @return 返回认证的用户信息
     */
    public SopAuthUser token(LoginCodeIn req) {
        SiPlatform plInfo = null;

        checkLoginCodeIn(req);

        SopAuthUser userInfo = userAuthService.token(SiAuthCode.builder()
                .code(req.getCode()).state(req.getState()).build());

        // 检查平台
        if (StrUtil.isNotBlank(userInfo.getCred().getBrief().getPlatformId())) {
            plInfo = platInfoGateway.getPlatformAndCheck(SiPlatIdFind.builder()
                    .platformId(userInfo.getCred().getBrief().getPlatformId()).build());
        }

        logLogin(userInfo, plInfo);

        return userInfo;
    }

    private void checkLoginCodeIn(LoginCodeIn req) {

    }

    public SopAuthUser login(CaptchaIn captchaIn, LoginContextIn req) {
        checkCaptcha(captchaIn);
        return login(req);
    }

    public SopAuthResult authorizeDirect(SopAuthDirectIn directIn) {
        if (null != directIn.getCaptchaKey()) {
            checkCaptcha(CaptchaIn.builder()
                    .key(directIn.getCaptchaKey())
                    .code(directIn.getCaptchaCode())
                    .build());
        }

        LoginCheckResult result = handleDirectLogin(directIn);
        SiCodeInfo codeInfo = userAuthService.authorize(result.getContext());
        return doCodeInfo(codeInfo);
    }

    public SopAuthUser loginDirect(SopAuthDirectIn directIn) {
        if (null != directIn.getCaptchaKey()) {
            checkCaptcha(CaptchaIn.builder()
                    .key(directIn.getCaptchaKey())
                    .code(directIn.getCaptchaCode())
                    .build());
        }

        LoginCheckResult result = handleDirectLogin(directIn);
        SopAuthUser userInfo = userAuthService.login(result.getUser(), result.getContext());

        logLogin(userInfo, result.getPlInfo());

        return userInfo;
    }

    @Override
    public SopAuthUser login(LoginContextIn req) {
        LoginCheckResult result = handleLogin(req);
        SopAuthUser userInfo = userAuthService.login(result.getUser(), result.getContext());

        logLogin(userInfo, result.getPlInfo());

        return userInfo;
    }

    public LoginCheckResult handleDirectLogin(SopAuthDirectIn directIn) {
        SiUser siUser;
        SiPlatform plInfo = null;
        String platformId;
        UserRoleInfo roleInfo;
        SiAssetBrief assetClient;
        SopAuthContext context = directIn.getContext();

        // 检查平台
        if (null != context.getPlatformId()) {
            plInfo = checkPlatform(context.getPlatformId(), context.getRealmId());
        }

        if (null != plInfo) {
            if (null == context.getRealmId()) {
                context.setRealmId(plInfo.getRealmId());
            }
            context.setRealmId(plInfo.getRealmId());
        }

        platformId = plInfo != null ? plInfo.getId() : "";

        assetClient = checkClientInfo(platformId, context.getClientId(), context.getClientSecret());

        /*
         * 1. 获取并校验用户
         */
        siUser = userInfoService.checkUser(UserIdFindIn
                .builder()
                .realmId(context.getRealmId())
                .platformId(platformId)
                .userId(directIn.getUserId())
                .build());

        roleInfo = userUtil.getUserRoleInfo(siUser.getId(), platformId, context.getClientId());

        /*
         * 2. 检查登录策略
         */
        strategyAuthGateway.checkStrategy(StrategyContext.builder()
                .userId(siUser.getId())
                .realmId(context.getRealmId())
                .terminalId(context.getTerminalCode())
                .clientId(context.getClientId())
                .clientSecret(context.getClientSecret())
                .platform(plInfo)
                .tenantId(context.getTenantId())
                .authType("")
                .build());

        return LoginCheckResult.builder()
                .user(siUser)
                .plInfo(plInfo)
                .platformId(platformId)
                .context(SiAuthContextIn
                        .builder()
                        .state("")
                        .realmId(context.getRealmId())
                        .userId(siUser.getId())
                        .peerIp(IPUtils.getIpAddr(getRequest()))
                        .account(directIn.getAccount())
                        .authType(directIn.getAuthType())
                        .authMode(directIn.getAuthMode())
                        .roleCodes(roleInfo.getRoleCodes())
                        .roleNames(roleInfo.getRoleNames())
                        .userType(siUser.getUserType())
                        .terminal(context.getTerminalCode())
                        .tenantId(context.getTenantId())
                        .platformId(platformId)
                        .accessExpired(assetClient.getProps().getInt("accessTimeout", 600))
                        .refreshExpired(assetClient.getProps().getInt("refreshTimeout", 600))
                        .clientId(context.getClientId())
                        .clientSecret(context.getClientSecret())
                        .ticketUri(null != plInfo ? plInfo.getTicketUrl() : "")
                        .grantType(GrantType.AUTHORIZATION_CODE)
                        .build())
                .build();
    }

    /**
     * 通过登录信息直接登录
     *
     * @param req 登录信息
     * @return 返回认证的用户信息
     */
    public LoginCheckResult handleLogin(LoginContextIn req) {
        SiUser siUser;
        AuthDetailIn detail = req.getAuthDetail();
        SiPlatform plInfo = null;
        String platformId;
        UserRoleInfo roleInfo;
        SiAssetBrief assetClient;

        // 校验参数是否正确
        checkLoginContextIn(req);

        // 校验用户校验信息（比如密码、短信验证码登）
        authCodeCheckGateway.check(req.getAuth());

        // 检查平台
        if (null != detail.getPlatformId()) {
            plInfo = checkPlatform(detail.getPlatformId(), req.getAccount().getRealmId());
        }

        if (null != plInfo) {
            if (null == req.getAccount().getRealmId()) {
                req.getAccount().setRealmId(plInfo.getRealmId());
            }
            detail.setRealmId(plInfo.getRealmId());
            req.getAccount().setRealmId(plInfo.getRealmId());
        }

        platformId = plInfo != null ? plInfo.getId() : "";

        assetClient = checkClientInfo(platformId, detail.getClientId(), detail.getClientSecret());

        /*
         * 1. 获取并校验用户
         */
        siUser = userInfoService.checkUser(CheckContextIn
                .builder()
                .account(req.getAccount())
                .auth(req.getAuth())
                .platformId(platformId)
                .build());

        roleInfo = userUtil.getUserRoleInfo(siUser.getId(), platformId, detail.getClientId());

        /*
         * 2. 检查登录策略
         */
        strategyAuthGateway.checkStrategy(StrategyContext.builder()
                .userId(siUser.getId())
                .realmId(detail.getRealmId())
                .terminalId(detail.getTerminal())
                .clientId(detail.getClientId())
                .clientSecret(detail.getClientSecret())
                .platform(plInfo)
                .tenantId(detail.getTenantId())
                .authType(req.getAuth().getAuthType())
                .build());

        return LoginCheckResult.builder()
                .user(siUser)
                .plInfo(plInfo)
                .platformId(platformId)
                .context(SiAuthContextIn
                        .builder()
                        .state(req.getAuthDetail().getState())
                        .realmId(req.getAuthDetail().getRealmId())
                        .userId(siUser.getId())
                        .peerIp(IPUtils.getIpAddr(getRequest()))
                        .account(req.getAccount().getAccount())
                        .authType(req.getAuth().getAuthType())
                        .authMode(req.getAuthMode())
                        .roleCodes(roleInfo.getRoleCodes())
                        .roleNames(roleInfo.getRoleNames())
                        .userType(siUser.getUserType())
                        .terminal(req.getAuthDetail().getTerminal())
                        .tenantId(req.getAuthDetail().getTenantId())
                        .platformId(platformId)
                        .accessExpired(assetClient.getProps().getInt("accessTimeout", 600))
                        .refreshExpired(assetClient.getProps().getInt("refreshTimeout", 600))
                        .clientId(req.getAuthDetail().getClientId())
                        .clientSecret(req.getAuthDetail().getClientSecret())
                        .ticketUri(null != plInfo ? plInfo.getTicketUrl() : req.getAuthDetail().getTicketUrl())
                        .grantType(GrantType.AUTHORIZATION_CODE)
                        .build())
                .build();
    }

    /**
     * 根据刷新令牌刷新访问令牌
     *
     * @param tokenInfo 刷新令牌
     * @return 返回重新生成后的认证信息
     */
    public Credential refresh(SopTokenInfo tokenInfo) {
        return userAuthService.refresh(tokenInfo);
    }

    /**
     * 根据访问令牌退出当前登录
     *
     * @param tokenIdInfo 访问令牌
     */
    public void logout(SopTokenInfo tokenIdInfo) {
        userAuthService.logoutSso(tokenIdInfo);
    }

    public void logoutUser(SopUserIdIn req) {
        userAuthService.logoutUser(req);
    }
    public void logoutSso(SopTokenInfo tokenIdInfo) {
        userAuthService.logoutSso(tokenIdInfo);
    }

    public void logoutSession(SopTokenInfo tokenIdInfo) {
        userAuthService.logoutSession(tokenIdInfo);
    }

    public String updatePassword(SopUpdatePasswordIn info) {
        userAuthService.updatePassword(info);
        return "OK";
    }

    /**
     * 用例：
     * 认证并登录平台
     * 根据平台标识和密钥认证并登录平台，此方法可以多次调用，
     * 如果重复调用则返回相同的token
     * 例外：
     * 1)如果平台不存在，则抛出异常
     * 2)平台密钥错误抛出异常
     */
    public SopAuthPlatform registerPlatform(PlatformAuthIn authIn) {
        PlatAuthVO platform;
        PlatformAuthInfo plInfo = new PlatformAuthInfo();

        plInfo.setRealmId(authIn.getRealmId());
        plInfo.setId(authIn.getPlatformId());
        plInfo.setSecret(authIn.getPlatformSecret());

        platform = siPlatformAuthService.register(plInfo);
        return platInfoGateway.getPlatAuthInfo(platform.getToken());
    }

    private void logLogin(SopAuthUser userInfo, SiPlatform platform) {
        SopUserBrief user = userInfo.getUser();
        SopCredential credential = userInfo.getCred();
        SopAuthBrief authBrief = credential.getBrief();
        LoginLogDTO loginLog = new LoginLogDTO();
        String roleNames = credential.getBrief().getRoleNames();
        String account = credential.getBrief().getAccount();
        String authType = credential.getBrief().getAuthType();

        loginLog.setRegion("中国");
        loginLog.setAuthType(authType);
        loginLog.setUserAccount(account);
        loginLog.setTenantId(authBrief.getTenantId());
        loginLog.setUserAgent(AuStrUtil.getContent(getUserAgent(), SizeConstDef.MAX_USER_AGENT));
        loginLog.setPlatformId(null == platform ? "" : AuStrUtil.getContent(
                platform.getId(), SizeConstDef.MAX_COMMON_ID));
        loginLog.setPlatformName(null == platform ? "" : AuStrUtil.getContent(
                platform.getName(),
                SizeConstDef.MAX_PLATFORM_NAME));
        loginLog.setClientId(AuStrUtil.getContent(authBrief.getClientId(),
                SizeConstDef.MAX_COMMON_ID));
        loginLog.setTerminalId(AuStrUtil.getContent(authBrief.getTerminalId(),
                SizeConstDef.MAX_COMMON_ID));
        loginLog.setUserId(AuStrUtil.getContent(user.getUserId(),
                SizeConstDef.MAX_COMMON_ID));
        loginLog.setUserName(AuStrUtil.getContent(
                StrUtil.isBlank(user.getNickName()) ? user.getName() : user.getNickName(),
                SizeConstDef.MAX_USER_NAME));
        loginLog.setUserRole(roleNames);
        loginLog.setPeerIp(AuStrUtil.getContent(AuthIPUtil.getIpAddr(getRequest()),
                SizeConstDef.MAX_PEER_IP));
        loginLog.setLoginTime(LocalDateTime.now());
        loginLog.setState("OK");
        loginLog.setReason("登录成功");

        try {
            loginLogApi.save(loginLog);
            lastLoginLogApi.save(loginLog);
            siUserService.updateLoginTime(SiUserLoginTimeIn.builder()
                    .realmId(platform.getRealmId())
                    .userId(user.getUserId())
                    .loginTime(LocalDateTime.now())
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getUserAgent() {
        String userAgent;
        HttpServletRequest request = getRequest();
        userAgent = request.getHeader(SopAuthConst.USER_BROWSER);

        if (StringUtil.isBlank(userAgent)) {
            userAgent = IPUtils.getUserAgent(request);
        }

        return userAgent;
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes;
        attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        return attributes.getRequest();
    }
}
